<?php
/**
 * 开放平台的微信用户
 *
 * Author: Zeon
 * Date: 2017/11/28
 * Created by Panxsoft.
 */
namespace App\Models\OpenWechat;

use App\Models\Admin\StoreAdmin;
use App\Models\Concerns\ScopeFilterable;
use App\Models\Customer\Customer;
use App\Models\MayPromoCoupon\MayPromoCoupon;
use App\Models\Panx\SkinDetector;
use App\Models\Panx\SkinRecordBonus;
use App\Models\Store\HmSkinRecord;
use App\Models\Store\SkinRecord;
use App\Models\Store\Store;
use App\Models\Store\WaxSkinRecord;
use App\Services\OpenWechat\Mock;
use App\Services\OpenWechat\WechatUserRepository;
use App\Services\Utils\UrlParser;
use App\Services\Utils\Uuid;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

/**
 * Class WechatUser
 *
 * @property string nickname_unicode - unicode格式的昵称，兼容emoji
 * @property int id - ID
 * @property int subscribe - 订阅状态
 * @property string openid - 微信唯一ID
 * @property string nickname - 从 UTF8 nickname_unicode 转成昵称 (getter)
 * @property string name - 用户信息，姓名
 * @property Carbon born_at
 * @property string ali_account
 * @property string language - (getter)
 * @property int authorizer_id
 *
 * @property StoreAdmin $storeAdmin
 * @property string headimgurl - (getter)
 * @property string phone
 * @property int customer_id
 * @property Carbon updated_at
 * @property int age - (getter) (setter)
 * @property string|null address
 * @property string|null city - (setter)
 * @property string|null province - (setter)
 * @property string|null country
 *
 * @property-write string last_hm_skin_info - 最近一次 hm 接触测肤数据
 * @property-write string last_wax_skin_info - 最近一次 wax 图像测肤数据
 * @property-write string province_city - 省 + 市
 * @property-write int all_skin_count - 测肤总次数
 * @property-write int hm_skin_count - hm 测肤次数
 * @property-write int wax_skin_count - wax 测肤次数
 * @property-write Collection|Store[] go_stores - 去过的店铺信息
 *
 * @property-read Authorizer|null authorizer
 * @property-read SkinRecord|null latestSkinRecordHm
 * @property-read SkinRecord|null latestSkinRecordWax
 * @property-read Customer|null customer - Only Web User belongs To Customer
 * @property-read SkinRecord[]|Collection skinRecords
 * @property-read Collection|Store[] stores
 * @property-write int skin_record_count - 测肤记录
 *
 * @method static self filterSubscribed(?bool $is_subscribed = true)
 * @method static self filterSkinRecordByStores(?int $has_skin_record, array $store_ids)
 * @method static self filterWaxSkinRecord(?bool $wax, array $skin_record_ids)
 * @method static self filterHmSkinRecord(?bool $hm, array $skin_record_ids)
 * @method static self filterSex(?int $sex)
 *
 * @package App\Models\Wechat
 * @mixin Builder
 */
class WechatUser extends Model
{
    use ScopeFilterable;

    const UN_SUBSCRIBE = 0;
    const SUBSCRIBED = 1;
    const LANGUAGE_ENGLISH = 'en';
    const LANGUAGE_CHINESE= 'zh-CN';

    const WEB_OPENID_PREFIX = 'web-';

    public $table = 'wechat_users';

    protected static $unguarded = true;

    const OPENID_MAGIC_MIRROR_ZEON = 'o3phBwujZ9FcygbvPL-teVISnugQ';

    const URL_PATH_WX = '/wx-system/';

    const URL_HASH_WX_USER = '#/user';
    
    /**
     * 微信填写个人信息的页面地址
     */
    const WX_USER_INFO_URL_FORMAT = '/wx-system/?%s#/information';
    
    /**
     * 微信个人中心首页
     */
    const WX_USER_CENTER_URL_FORMAT = '/wx-system/?%s#/user';

    /**
     * 微信肤质详情页面路由
     */
    const WX_SKIN_REPORT_URL_FORMAT = '/wx-system/?%s#/report';

    /**
     * 微信扫码收藏妆容截图登录页面
     */
    const WX_FAVOR_LOGIN_URL_FORMAT = '/wx-system/?%s#/login';

    /**
     * 微信扫码进入卡包的页面
     */
    const WX_COUPON_CARD_URL_FORMAT = '/wx-system/?%s#/card';

    /**
     * 微信扫码进入消消乐的页面
     */
    const WX_BOOM_GAME_URL_FORMAT = '/wx-games/index.html?%s';

    protected $casts = [
        'authorizer_id' => 'int',
        'customer_id'   => 'int',
        'subscribe'     => 'int',
        'phone'         => 'string',
        'born_at'       => 'date',
        'longitude'     => 'float',
        'latitude'      => 'float',
    ];

    protected $guarded = [];

    /**
     * @var array - 默认返回年龄
     */
    protected $appends = ['age'];

    /**
     * @var array - 默认不显示
     */
    protected $hidden = ['nickname_unicode'];

    public function isWebUser(string $openid = null): bool
    {
        return starts_with($openid ?? $this->openid, self::WEB_OPENID_PREFIX);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function authorizer()
    {
        return $this->belongsTo(Authorizer::class);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
     */
    public function skinRecordBonuses()
    {
        return $this->morphMany(SkinRecordBonus::class, 'bonusable');
    }


    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function storeAdmin()
    {
        return $this->hasOne(StoreAdmin::class,'wechat_user_id','id');
    }

    /**
     * @return HasMany
     */
    public function skinRecords()
    {
        return $this->hasMany(SkinRecord::class, 'wechat_user_id', 'id');
    }

    /**
     * @return string
     */
    public function decodeNickName(): ?string
    {
        // "ext-json": "*",
        return json_decode($this->nickname_unicode);
    }

    /**
     * @used-by nickname
     *
     * @return null|string
     */
    public function getNicknameAttribute()
    {
        return $this->decodeNickName();
    }
    /**
     * 保存未订阅只是通过网页授权的微信用户数据
     *
     * @param array $user_info
     * @see Mock::userGetOriginal()
     *
     * @return bool
     */
    public function updateOauthOriginUserInfo(array $user_info)
    {
        return $this->update([
            "openid"     => $user_info["openid"],
            "nickname"   => $user_info["nickname"],
            "sex"        => $user_info["sex"],
            "language"   => $user_info["language"],
            "city"       => $user_info["city"],
            "province"   => $user_info["province"],
            "country"    => $user_info["country"],
            "headimgurl" => $user_info["headimgurl"],

            "authorizer_id" => $user_info["authorizer_id"],
            'nickname_unicode' => json_encode($user_info['nickname']),
        ]);
    }

    /**
     * @deprecated - 保存网页授权已订阅微信用户数据
     * @see WechatUserRepository::createOrUpdateWechatUserInfo() - 请使用这个
     *
     * @param array $user_info
     * @see Mock::appUserGet()
     *
     * @return bool
     */
    public function updateOauthAppUserGetInfo(array $user_info)
    {
        return $this->update([
            "openid"         => $user_info["openid"],
            "nickname"       => $user_info["nickname"],
            "sex"            => $user_info["sex"],
            "language"       => $user_info["language"],
            "city"           => $user_info["city"],
            "province"       => $user_info["province"],
            "country"        => $user_info["country"],
            "headimgurl"     => $user_info["headimgurl"],
            "subscribe"      => $user_info["subscribe"],
            "subscribe_time" => $user_info["subscribe_time"],
            "subscribe_scene"=> $user_info["subscribe_scene"],
            "remark"         => $user_info["remark"],

            "authorizer_id"  => $user_info["authorizer_id"],
            'nickname_unicode' => json_encode($user_info['nickname']),
        ]);
    }

    /**
     * @param string $openid
     * @return self|Model
     */
    public static function firstOrCreateByOpenId($openid)
    {
        return (new self)->firstOrCreate([
            'openid' => $openid
        ]);
    }

    /**
     * 返回一个mock数据
     *
     *
     * @return WechatUser
     */
    public static function mockUser()
    {
        // !!! 不要修改Example方法中的示例数据 !!!
        $user = new self(Mock::appUserGet());

        // 如果需要扩展请在这里修改 ...
//        $user->openid = '你要测试的OPENID';

        return $user;
    }

    /**
     * @param $openid
     * @return self|null
     */
    public static function findByOpenId(string $openid = null): ?self
    {
        return (new self)->where('openid', $openid)->first();
    }

    /**
     * @param $openid
     * @return mixed
     */
    public static function unSubscribeByOpenId(string $openid = null): bool
    {
        $user = self::findByOpenId($openid);

        if ($user) {
            return $user->unSubscribe();
        }

        return false;
    }

    /**
     * @return bool
     */
    private function unSubscribe(): bool
    {
        return $this->update([
            'subscribe' => self::UN_SUBSCRIBE,
        ]);
    }

    /**
     * 是否在网页中填写过个人信息
     *
     * @return bool
     */
    public function hasPersonalInfo(): bool
    {
        return (bool) $this->born_at;
    }

    /**
     * @used-by age - getter
     *
     * @return int
     */
    public function getAgeAttribute(): ?int
    {
        if (! $this->born_at) return null;

        return $this->born_at->diffInYears(now()) + 1;
    }

    /**
     * @used-by age - setter
     *
     * @param int $age
     *
     * @return void
     */
    public function setAgeAttribute(int $age): void
    {
        // born at half of year before
        $this->born_at = now()->addMonth(6)->subYear($age);
    }

    /**
     * @param Builder $builder
     * @param bool|null $is_subscribed
     * @return Builder
     */
    public function scopeFilterSubscribed(Builder $builder, ?bool $is_subscribed = true)
    {
        return $builder->where('subscribe', $is_subscribed);
    }

    /**
     * @used-by filterSkinRecordByStores() - 过滤在指定店铺有或没有测肤记录的微信用户
     *
     * @param Builder $builder
     * @param $has_skin_record
     * @param array $store_ids
     *
     * @return Builder|\Illuminate\Database\Query\Builder
     */
    public function scopeFilterSkinRecordByStores(Builder $builder, ?int $has_skin_record, array $store_ids)
    {
        if (is_null($has_skin_record)) {
            return $builder;
        }

        if ($has_skin_record) {
            return $builder->whereHas('skinRecords', function (Builder $builder) use ($store_ids)  {
                $builder->whereIn('store_id', $store_ids);
            });
        } else {
            return $builder->whereDoesntHave('skinRecords', function (Builder $builder) use ($store_ids)  {
                $builder->whereIn('store_id', $store_ids);
            });
        }
    }

    /**
     * @used-by filterHmSkinRecord
     *
     * @param Builder $builder
     * @param $hm
     * @param $skin_record_ids
     *
     * @return Builder
     */
    public function scopeFilterHmSkinRecord(Builder $builder, ?bool $hm, array $skin_record_ids)
    {
        if (! $hm) return $builder;

        $hm_wechat_user_ids = (new HmSkinRecord)
            ->whereIn('skin_record_id', $skin_record_ids)
            ->pluck('wechat_user_id')
            ->unique()
            ->all();

        return $builder->whereKey($hm_wechat_user_ids);
    }

    /**
     * @used-by filterWaxSkinRecord
     *
     * @param Builder $builder
     * @param $wax
     * @param $skin_record_ids
     *
     * @return Builder
     */
    public function scopeFilterWaxSkinRecord(Builder $builder, ?bool $wax, array $skin_record_ids)
    {
        if (! $wax) return $builder;

        $has_wax_records_wx_user_ids = (new WaxSkinRecord)
            ->whereIn('skin_record_id', $skin_record_ids)
            ->pluck('wechat_user_id')
            ->unique()
            ->all();

        return $builder->whereKey($has_wax_records_wx_user_ids);
    }

    /**
     * @used-by filterSex
     *
     * @param Builder $query
     * @param int|null $sex
     *
     * @return Builder
     */
    public function scopeFilterSex(Builder $query, ?int $sex)
    {
        if (is_null($sex)) return $query;

        return $query->where('sex', $sex);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function mayPromoCoupons()
    {
        return $this->hasMany(MayPromoCoupon::class,'wechat_user_id','id');
    }

    /**
     * @used-by language
     *
     * @return string
     */
//    public function getLanguageAttribute()
//    {
//        $attr_lang = $this->attributes['language'] ?? null;
//        if ($attr_lang === self::LANGUAGE_ENGLISH) {
//            return self::LANGUAGE_ENGLISH;
//        } else {
//            return self::LANGUAGE_CHINESE;
//        }
//    }

    /**
     * @used-by $latestSkinRecord
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne|\Illuminate\Database\Query\Builder
     */
    public function latestSkinRecord()
    {
        return $this
            ->hasOne(SkinRecord::class,'wechat_user_id','id')
            ->whereIn('skin_detector_id', SkinDetector::validatedIds())
            ->latest();
    }

    /**
     * @used-by $latestSkinRecordHm
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne|\Illuminate\Database\Query\Builder
     */
    public function latestSkinRecordHm()
    {
        return $this
            ->hasOne(SkinRecord::class,'wechat_user_id','id')
            ->where('skin_detector_id', SkinDetector::ID_HM)
            ->latest();
    }

    /**
     * @used-by $latestSkinRecordWax
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne|\Illuminate\Database\Query\Builder
     */
    public function latestSkinRecordWax()
    {
        return $this
            ->hasOne(SkinRecord::class,'wechat_user_id','id')
            ->where('skin_detector_id', SkinDetector::ID_WAX)
            ->latest();
    }


    public function hmSkinRecords()
    {
        return $this->hasMany(HmSkinRecord::class,'wechat_user_id','id');
    }

    public function waxSkinRecords()
    {
        return $this->hasMany(WaxSkinRecord::class,'wechat_user_id','id')
            ->whereIn('itemsid',WaxSkinRecord::ITEMSID_ALL);
    }

    public function stores()
    {
        return $this->belongsToMany(Store::class,'store_pivot_wechat_user','wechat_user_id', 'store_id');
    }

    public function customer()
    {
        return $this->belongsTo(Customer::class,'customer_id','id');
    }

    /**
     * App 微信扫码用户登录后，用户收到的个人中心页面的跳转地址
     *
     * @test
     *
     * @param bool $with_openid
     *
     * @return string
     */
    public function wxUserCenterUrl(bool $with_openid = false): string
    {
        $query = [
            'appid' => $this->authorizer->authorizer_appid ?? 'ERROR_APPID_MISSING',
            'lang'  => $this->language ?? 'zh-CN'
        ];

        if ($with_openid) {
            $query['openid'] = $this->openid;
        }

        $query_str = http_build_query($query);

        return url(sprintf(self::WX_USER_CENTER_URL_FORMAT, $query_str));
    }

    public static function generateWebOpenid(): string
    {
        return self::WEB_OPENID_PREFIX . Uuid::generate();
    }

    public function isSubscribed()
    {
        return $this->subscribe === self::SUBSCRIBED;
    }

    /**
     * 防止中间件频繁请求微信更新信息
     *
     * @return bool
     */
    public function needQueryUpdateInfo(): bool
    {
        return $this->updated_at->diffInMinutes(now()) >= 10;
    }

    /**
     * @used-by headimgurl
     *
     * @return null|string
     */
    public function getHeadimgurlAttribute()
    {
        $url = $this->attributes['headimgurl'] ?? null;

        return $url
            ? UrlParser::toSecure($url)
            : $url;
    }

    /**
     * @used-by province
     * @test
     *
     * @param null|string $province
     */
    public function setProvinceAttribute(?string $province): void
    {
        $this->attributes['province'] = str_replace(['省', '市', '特别行政区', '自治区', '维吾尔', '壮族', '回族'], '', $province);
    }

    /**
     * @used-by city
     * @test
     *
     * @param null|string $city
     */
    public function setCityAttribute(?string $city): void
    {
        $this->attributes['city'] = str_replace(['市'], '', $city);
    }
    
    /**
     * 微信扫码跳转填写用户信息的地址
     *
     * @param $scan_event_id
     * @return string
     */
    public function appWechatScanLoginFillUserInfoUrl(int $scan_event_id): string
    {
        $openid = $this->openid;
        $appid = $this->authorizer->authorizer_appid ?? 'ERROR_APPID_MISSING';
        $lang = $this->language;
        $query = http_build_query(compact('appid', 'openid', 'scan_event_id', 'lang'));
        
        return url(sprintf(self::WX_USER_INFO_URL_FORMAT, $query));
    }

    /**
     * 微信扫码进入卡包
     *
     * @param $scan_event_id
     * @return string
     */
    public function appWechatScanCouponCardUrl(int $scan_event_id): string
    {
        $openid = $this->openid;
        $appid = $this->authorizer->authorizer_appid ?? 'ERROR_APPID_MISSING';
        $lang = $this->language;
        $query = http_build_query(compact('appid', 'openid', 'scan_event_id', 'lang'));

        return url(sprintf(self::WX_COUPON_CARD_URL_FORMAT, $query));
    }

    /**
     * 微信扫码进入消消乐游戏
     *
     * @param ScanEvent $scan_event
     *
     * @return string
     */
    public function appWechatScanBoomGameUrl(ScanEvent $scan_event): string
    {
        $scan_event_id = $scan_event->getKey();
        $customer_id = $scan_event->data['customer_id'] ?? Customer::ID_ADMIN;
        $openid = $this->openid; // 暂时不返回 openid, 否则链接就不能分享给其他人了
        $appid = $this->authorizer->authorizer_appid ?? 'ERROR_APPID_MISSING';
        $lang = $this->language;
        $query = http_build_query(compact('appid', 'scan_event_id', 'customer_id', 'lang'));

        return url(sprintf(self::WX_BOOM_GAME_URL_FORMAT, $query));
    }
}
